package lumis.portal.sendmail.smtp;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import lumis.portal.PortalException;
import lumis.portal.UnexpectedException;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.dao.DaoFactory;
import lumis.portal.sendmail.IMailAttachment;
import lumis.portal.sendmail.IMailBody;
import lumis.portal.sendmail.IMailConfig;
import lumis.portal.sendmail.IMailSender;
import lumis.portal.sendmail.ISingleMail;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.util.ITransaction;

import org.apache.commons.mail.ByteArrayDataSource;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.apache.commons.mail.MultiPartEmail;

/**
 * Sends an e-mail using a SMTP connection.
 * 
 * @version $Revision: 11394 $ $Date: 2010-04-08 17:16:31 -0300 (Thu, 08 Apr 2010) $
 * @since 4.0.4
 */
public class SmtpMailSender implements IMailSender
{
	public void sendMail(SessionConfig sessionConfig, ISingleMail mail, ITransaction transaction) throws PortalException
	{
		try
		{
			IMailConfig mailConfig = getMailConfig();
			MultiPartEmail email = null;
			
			// if there is htmlMsg, email will be HtmlEmail, else it will be a MultiPartEmail
			IMailBody body = mail.getBody();
			String htmlMsg = body.getHtmlMsg();
			if (htmlMsg != null)
				email = new HtmlEmail();
			else
				email = new MultiPartEmail();
			
			// set generic email properties
			email.setCharset(mail.getCharset());
			Collection<String> tos = getEmailDestinationStringAsCollection(mail.getTo());
			for (String to: tos)
				email.addTo(getEmailAddress(to), getEmailName(to));
			Collection<String> ccs = getEmailDestinationStringAsCollection(mail.getCc());
			for (String cc: ccs)
				email.addCc(getEmailAddress(cc), getEmailName(cc));
			Collection<String> bccs = getEmailDestinationStringAsCollection(mail.getBcc());
			for (String bcc: bccs)
				email.addBcc(getEmailAddress(bcc), getEmailName(bcc));
			String from = mail.getFrom();
			if (from != null && from.length() > 0)
				email.setFrom(getEmailAddress(from), getEmailName(from));
			Collection<String> replyTos = getEmailDestinationStringAsCollection(mail.getReplyTo());
			for (String replyTo: replyTos)				
				email.addReplyTo(getEmailAddress(replyTo), getEmailName(replyTo));
			String subject = mail.getSubject();
			email.setSubject(subject);
			
			// set email body message
			String textMsg = body.getTextMsg();
			if (email instanceof HtmlEmail)
			{
				HtmlEmail htmlEmail = (HtmlEmail)email;
				htmlEmail.setHtmlMsg(body.getHtmlMsg());
				if (textMsg != null && textMsg.length() > 0)
					htmlEmail.setTextMsg(body.getTextMsg());
			}
			else
			{
				if (textMsg != null && textMsg.length() > 0)
					email.setMsg(textMsg);				
			}

			// do the attachments
			Collection<IMailAttachment> attachments = body.getAttachments();
			for (IMailAttachment attachment: attachments)
			{
				ByteArrayDataSource dataSource = new ByteArrayDataSource(attachment.getContentInputStream(), attachment.getContentType());
				email.attach(dataSource, attachment.getName(), attachment.getDescription());
			}
						
			// set host and autentication properties
			email.setHostName(mailConfig.getSmtpHost());
			email.setSmtpPort(mailConfig.getSmtpPort());
			if (mailConfig.getSmtpUser() != null)
				email.setAuthentication(mailConfig.getSmtpUser(), mailConfig.getSmtpPassword());
			
			if (mailConfig.getSocketTimeout() != 0) {
				email.setSocketTimeout(mailConfig.getSocketTimeout());
			}
			
			if (mailConfig.getSocketConnectionTimeout() != 0) {
				email.setSocketConnectionTimeout(mailConfig.getSocketConnectionTimeout());
			}
			
			if (IMailConfig.CryptographicProtocol.SSL.equals(mailConfig.getCryptographicProtocol()))
			{
				email.setSSL(true);
			}
			else if (IMailConfig.CryptographicProtocol.TLS.equals(mailConfig.getCryptographicProtocol()))
			{
				email.setTLS(true);
			}			
			
			// send the email
			email.send();
		}
		catch (IOException e)
		{
			throw new UnexpectedException(e);
		}
		catch (EmailException e)
		{
			throw new UnexpectedException(e);
		}
	}
	
	/**
	 * Returns the MailConfig with the configurations to be used when sending the e-mail.
	 * @return the MailConfig.
	 * @throws PortalException if the MailConfig could not be obtained.
	 * @since 4.0.4
	 */
	protected IMailConfig getMailConfig() throws PortalException
	{
		ITransaction transaction = PortalTransactionFactory.createTransaction();
		try
		{
			transaction.begin();
			IMailConfig mailConfig = DaoFactory.getSendMailDao().getMailConfig(transaction);
			transaction.commit();
			
			return mailConfig;
		}
		finally
		{
			transaction.dispose();
		}
	}

	/** 
	 * Regexp pattern used by {@link #getEmailAddress(String)} to fetch the email address. 
	 * @since 4.0.4
	 */
	private static final Pattern emailAddressPattern = Pattern.compile("<(.*)>");
	
	/**
	 * Return the email address from an email String.
	 * @param emailAddress the email string in the format 'Name <email@address.com>'.
	 * @return the email address.
	 * @since 4.0.4
	 */
	private String getEmailAddress(String emailAddress)
	{
		Matcher matcher = emailAddressPattern.matcher(emailAddress);
		if (matcher.find())
			return matcher.group(1).trim();
		else
			return emailAddress;
	}
	
	/** 
	 * Regexp pattern used by {@link #getEmailName(String)} to fetch the email name. 
	 * @since 4.0.4
	 */
	private static final Pattern emailNamePattern = Pattern.compile("^([^<]*)");
	
	/**
	 * Return the email name from an email String.
	 * @param emailAddress the email string in the format 'Name <email@address.com>'.
	 * @return the email name.
	 * @since 4.0.4
	 */
	private String getEmailName(String emailAddress)
	{
		Matcher matcher = emailNamePattern.matcher(emailAddress);
		if (matcher.find())
		{
			String name = matcher.group(1);
			return name.replace('"', ' ').trim();
		}
		else
			return null;
	}
	
	/**
	 * Translates an email destination string into a collection of Strings, each
	 * one contaning an email destination. The email destination string may contain
	 * several emails separated by comma or semi-colon.
	 * @param destination the email destination string.
	 * @return a collection of strings, where each string is one email in the given 
	 * email destination string.
	 * @since 4.0.4
	 */
	protected Collection<String> getEmailDestinationStringAsCollection(String destination)
	{
		if (destination == null)
			return Collections.<String>emptyList();
		
		StringTokenizer mailListTokenizer = new StringTokenizer(destination, ",;", false);
		Collection<String> result = new ArrayList<String>(mailListTokenizer.countTokens());
		while (mailListTokenizer.hasMoreTokens())
		{
			result.add(mailListTokenizer.nextToken());
		}
		return result;
	}
}
